Skip to content

Populate service map when bootstrap is skipped (e.g. under Rector)#996

Open
bbrala wants to merge 1 commit into
mglaman:mainfrom
bbrala:service-map-under-rector
Open

Populate service map when bootstrap is skipped (e.g. under Rector)#996
bbrala wants to merge 1 commit into
mglaman:mainfrom
bbrala:service-map-under-rector

Conversation

@bbrala

@bbrala bbrala commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #995.

The service map is built by DrupalAutoloader::register(), which runs as a bootstrapFiles entry. PHPStan executes those through CommandHelper::executeBootstrapFile(). Tools that build the PHPStan container directly never call CommandHelper, so the bootstrap is skipped and the map stays empty. Rector is the case — its PHPStanServicesFactory calls ContainerFactory::create() and stops there — so \Drupal::service('renderer') resolves to object instead of Drupal\Core\Render\Renderer, and type-aware Rector rules silently skip those nodes.

This registers RectorServiceMapInitializer, a no-op DynamicStaticMethodReturnTypeExtension that PHPStan instantiates eagerly when the broker builds. If the service map is still empty at that point, it runs the autoloader against the live container. The empty-map guard keeps a normal phpstan analyse run a no-op, since the bootstrap already populated the map there.

I went with reusing DrupalAutoloader::register() wholesale rather than refactoring the scan out of it, to keep the change small. Lazy population inside ServiceMap would be cleaner as the real shape if you'd prefer that — happy to rework.

Tested against Drupal 11 with Rector 2.4 across a full install (webform, photoswipe): \Drupal::service('renderer') resolves to Renderer and \Drupal::service('entity_type.manager') to EntityTypeManager, Rector processes both modules without errors, and standalone phpstan analyse behaves exactly as before.


Claude was used for research and development of this issue and fix.

The service map is built by DrupalAutoloader::register(), which runs as a
bootstrapFiles entry. PHPStan executes those via CommandHelper. Tools that build
the PHPStan container directly (Rector's PHPStanServicesFactory) never call
CommandHelper, so the bootstrap is skipped and the map stays empty —
\Drupal::service() then resolves to object instead of the concrete class.

Register a guarded, eagerly-instantiated no-op return type extension that runs
the autoloader against the live container when the map is still empty. A normal
phpstan analyse run stays a no-op because the bootstrap already filled the map.

Refs mglaman#995
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Service map is empty when running under Rector — bootstrap never executes

1 participant